<?php
/**
 * Created by PhpStorm.
 * User: linhongzhao
 * Date: 18-5-11
 * Time: 下午9:20
 */

namespace EFrame;

use Psr\Log\AbstractLogger;

class Logger extends AbstractLogger
{
    const CONTEXT_KEY_PREFIX = '{%';
    const CONTEXT_KEY_END = '%}';
    const MAX_TRACE_DEPTH = 5;

    protected $logDir = '';
    protected $trace = false;

    /**
     * Logger constructor.
     * TODO 使用前，建议实现子类重写construct，以按需重新定义相关配置信息的初始化过程
     */
    public function __construct()
    {
        // TODO 之所以在construct中初始化日志路径可以方便通过获取配置文件初始化
        // TODO 如果不需要通过配置文件初始化，建议直接在变量定义处写定路径
        $this->logDir = __DIR__ . '/../log';
        if (!is_dir($this->logDir)) {
            mkdir($this->logDir);
        }
    }

    /**
     * 日志终点，所有写操作最终在这里汇集
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     * @return bool
     * @uses file_put_contents()
     */
    public function log($level, $message, array $context = array())
    {
        $log_file = $this->logDir . '/' .
            $level . '.' . date('Ymd') . '.log';

        // format
        if (!empty($context)) {
            $message = $this->replace($message, $context);
        }
        $message .= PHP_EOL;

        if ($this->trace > 0) {
            $message .= $this->backtrace();
        }

        // write
        $res = file_put_contents($log_file, $message, FILE_APPEND|LOCK_EX);

        if ($res === false) {
            return false;
        }
        return true;
    }

    /**
     * 是否打印回溯信息及回溯深度
     * @param integer $trace <=0-不打印,>0-回溯深度
     * @return self 返回当前对象，便于链式操作
     * @see Logger::MAX_TRACE_DEPTH
     */
    public function trace($trace = 3)
    {
        if ($trace > self::MAX_TRACE_DEPTH) {
            $this->trace = self::MAX_TRACE_DEPTH;
        } else {
            $this->trace = $trace;
        }
        return $this;
    }

    /**
     * 回溯信息字符串整理
     * @return string 回溯生成字符串
     */
    protected function backtrace()
    {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $this->trace+2);
        $str = '';
        for ($key=0; $key<$this->trace; $key++) {
            $cursor = $key+2;
            if (!isset($backtrace[$cursor])) {
                break;
            }
            $record = $backtrace[$cursor];
            $str .= '#' . $key . ' ' .
                $record['function'] . '() [' .
                $record['file'] . ':' .
                $record['line'] . ']' .
                PHP_EOL;
        }
        return $str;
    }

    /**
     * @param $message
     * @param $context
     * @return string
     */
    protected function replace($message, $context)
    {
        $buffer = [];
        foreach ($context as $key => $value) {
            if (!is_array($value) && (!is_object($value) || method_exists($value, '__toString'))) {
                $key = self::CONTEXT_KEY_PREFIX . $key . self::CONTEXT_KEY_END;
                $buffer[$key] = $value;
            }
        }
        return strtr($message, $buffer);
    }
}